﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Configuration;
using System.Data.SqlClient;
using System.Drawing.Imaging;
using System.IO;
using System.Text;
using System.Reflection;

namespace blackhouse.Web {
	public class DbImages : IHttpHandler {

		#region IHttpHandler Members

		public bool IsReusable {
			get { return false; }
		}

		public void ProcessRequest(HttpContext context) {
			try {
				this.process(context);
			} catch(Exception e) {
				context.Response.Clear();
				context.Response.Write(e);
			}
		}

		#endregion

		#region Fields

		private Size boxSize = Size.Empty;
		private string eTag = "";
		private string eTagRequest = "";
		private string fileExtenstion = "";
		private string fileName = "";
		private string fileNameWithExtension = "";

		#endregion

		#region Properties

		/// <summary>
		/// If Image not found return box 1x1 px of this color. If color not set throw error
		/// </summary>
		public Color ColorOfEmptyImage { get; set; }

		#endregion

		#region Methods

		private bool checkExtension(string extension) {
			return (extension == "gif" || extension == "jpg" || extension == "png");
		}

		private Size getBoxSize(Uri url) {
			Regex findSizeInfo = new Regex(@"(\d*)x(\d*)/");
			Match finded = findSizeInfo.Match(url.ToString());
			if(finded.Success) {
				int width = Int32.Parse(finded.Groups[1].Value);
				int height = Int32.Parse(finded.Groups[2].Value);
				Size size = new Size(width, height);
				return size;
			}
			return Size.Empty;
		}

		protected virtual string getConnectionString() {
			return ConfigurationManager.AppSettings["MSSQLConnectionString"];
		}

		private string getFileExtension(Uri url) {
			string fExt = url.Segments.Last();
			fExt = fExt.ReplaceEx(".axd", "");
			fExt = fExt.Substring(fExt.LastIndexOf('.') + 1);
			return fExt.ToLower();
		}

		private string getFileName(Uri url) {
			int segmentIndex = url.Segments.Length - 2;
			string fileName = url.Segments.GetValue(segmentIndex).ToString().Trim('/');
			return fileName;
		}

		private string getFileNameWithExtension(Uri url) {
			return this.getFileNameWithExtension(this.getFileName(url), this.getFileExtension(url));
		}

		private string getFileNameWithExtension(string fileName, string fileExtension) {
			return string.Format("{0}.{1}", fileName, fileExtension);
		}

		private string getGuid(string fileName) {
			Guid guid = Guid.Empty;
			using(SqlConnection connection = new SqlConnection(getConnectionString())) {
				connection.Open();
				SqlCommand command = connection.CreateCommand();
				command.CommandType = System.Data.CommandType.StoredProcedure;
				command.CommandTimeout = 0;
				command.CommandText = this.getProcedureFileGuid();
				command.Parameters.Add("@FileName", System.Data.SqlDbType.NVarChar, 400).Value = fileName;
				object objGuid = command.ExecuteScalar();
				if(objGuid != null)
					guid = (Guid) objGuid;
				connection.Close();
			}
			return guid.ToString();
		}

		public static string GetImageUrl(string icon, Size size) {
			return GetImageUrl(icon, size, "DbImages");
		}

		public static string GetImageUrl(string icon, Size size, string handlerFileName) {
			string[] parts = icon.Split('.');
			if(parts.Length != 2) return "";
			StringBuilder buildUrl = new StringBuilder("/");
			if(!size.IsEmpty)
				buildUrl.AppendFormat("{0}x{1}/", size.Width, size.Height);
			buildUrl.Append(string.Join(".", parts.Take(parts.Length - 1).ToArray()) + "/" + handlerFileName + "." + parts.Last());
			return buildUrl.ToString();
		}

		protected virtual string getProcedureFile() {
			return "bhp_GetFile";
		}

		protected virtual string getProcedureFileGuid() {
			return "bhp_GetFileGuid";
		}

		private bool isGZipSupported() {
			string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
			if(!string.IsNullOrEmpty(acceptEncoding) &&
				 (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
				return true;
			return false;
		}

		private bool isModified(string currentETag, string requestETag) {
			if(HttpContext.Current.Request["nocache"] == "1") return true;
			return currentETag != requestETag;
		}

		private ImageCodecInfo GetEncoder(ImageFormat format) {
			ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
			foreach(ImageCodecInfo codec in codecs) {
				if(codec.FormatID == format.Guid) {
					return codec;
				}
			}
			return null;
		}

		private Image resize(Image bitmap, Size box) {
			if(!box.IsEmpty && (box.Width < bitmap.Width || box.Height < bitmap.Height)) {
				double resizeValue = 1;
				double resizeWidth, resizeHeight;
				resizeHeight = 1d * box.Width / bitmap.Width;
				resizeWidth = 1d * box.Height / bitmap.Height;
				resizeValue = (resizeHeight < resizeWidth) ? resizeHeight : resizeWidth;

				int width = (int) Math.Floor(bitmap.Width * resizeValue);
				int height = (int) Math.Floor(bitmap.Height * resizeValue);
				Image thumb = new Bitmap(width, height);
				Graphics gp = Graphics.FromImage(thumb);
				gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
				gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
				gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
				gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
				gp.DrawImage(bitmap, 0, 0, width, height);
				bitmap.Dispose();
				gp.Dispose();
				return thumb;
			} else {
				Image thumb = new Bitmap(bitmap.Width, bitmap.Height);
				Graphics gp = Graphics.FromImage(thumb);
				gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
				gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
				gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
				gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
				gp.DrawImage(bitmap, 0, 0, bitmap.Width, bitmap.Height);
				bitmap.Dispose();
				return thumb;
			}
		}

		private void addWatermark(ImageWatermark watermark, Graphics mainPhoto, Size mainPhotoSize) {
			using(Stream watermarkStream = watermark.GetImageStream()) {
				if(watermarkStream != null) {
					using(Bitmap bp = new Bitmap(watermarkStream)) {
						Size watermarkSize = new Size();
						watermarkSize.Width = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Width * watermark.PercentWidth / 100d));
						watermarkSize.Height = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Height * watermark.PercentHeight / 100d));
						using(Image bp2 = this.resize(bp, watermarkSize)) {
							Point watermarkPosition = new Point();
							watermarkPosition.X = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Width * watermark.PercentPositionX / 100d));
							if(watermark.PercentPositionX < 0)
								watermarkPosition.X = mainPhotoSize.Width + watermarkPosition.X - bp2.Size.Width;
							watermarkPosition.Y = Convert.ToInt32(System.Math.Ceiling(mainPhotoSize.Height * watermark.PercentPositionY / 100d));
							if(watermark.PercentPositionY < 0)
								watermarkPosition.Y = mainPhotoSize.Height + watermarkPosition.Y - bp2.Size.Height;
							mainPhoto.DrawImage(bp2, watermarkPosition);
						}
					}
				}
			}
		}

		protected virtual ImageWatermark[] getWatermarks() {
			return new ImageWatermark[0];
		}

		private void process(HttpContext context) {
			try {
				this.fileExtenstion = this.getFileExtension(context.Request.Url);

				if(this.checkExtension(this.fileExtenstion)) {
					this.fileName = this.getFileName(context.Request.Url);
					this.fileNameWithExtension = this.getFileNameWithExtension(this.fileName, this.fileExtenstion);
					this.boxSize = this.getBoxSize(context.Request.Url);
					this.setTags(this.fileNameWithExtension, this.boxSize, context.Request);
					this.setHeadersForAll(context.Response, this.eTag);
					if(this.isModified(this.eTag, this.eTagRequest)) {
						this.setHeadersForImage(context.Response, this.fileExtenstion);
						this.setGZip();
						ImageFormat imageFormat = this.getImageFormat(this.fileExtenstion);
						Image bitmap = this.readImage(imageFormat, this.fileNameWithExtension);
						if(bitmap == null) {
							if(this.ColorOfEmptyImage == null)
								throw new DbImagesNotFoundException();
							else {
								bitmap = new Bitmap(1, 1);
								Graphics gph = Graphics.FromImage(bitmap);
								gph.Clear(this.ColorOfEmptyImage);
								gph.Flush();
							}
						}
						Image image = this.resize(bitmap, this.boxSize);
						Graphics gp = Graphics.FromImage(image);
						gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
						gp.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
						gp.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
						gp.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
						foreach(ImageWatermark watermark in this.getWatermarks()) {
							this.addWatermark(watermark, gp, image.Size);
						}
						this.writeImageToResponse(image, imageFormat, context.Response);
						bitmap.Dispose();
						image.Dispose();
						gp.Dispose();
					} else
						this.setIsNotModified(context.Response);

				}
			} catch(Exception e) {
				HttpContext.Current.Response.ContentType = "text/plain";
				HttpContext.Current.Response.Write(e);
			}
		}

		private Image readImage(ImageFormat imageFormat, string fileNameWithExtension) {
			byte[] data = null;
			using(SqlConnection connection = new SqlConnection(getConnectionString())) {
				connection.Open();
				SqlCommand command = connection.CreateCommand();
				command.CommandType = System.Data.CommandType.StoredProcedure;
				command.CommandTimeout = 0;
				command.CommandText = this.getProcedureFile();
				command.Parameters.Add("@FileName", System.Data.SqlDbType.NVarChar, 400).Value = fileNameWithExtension;
				data = (byte[]) command.ExecuteScalar();
			}
			if(data != null) {
				using(MemoryStream mst = new MemoryStream()) {
					mst.Write(data, 0, data.Length);
					return Image.FromStream(mst);
				}
			}
			return null;
		}

		private void setGZip() {
			if(isGZipSupported()) {
				HttpResponse response = HttpContext.Current.Response;

				string acceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
				if(acceptEncoding.Contains("gzip")) {
					response.Filter = new System.IO.Compression.GZipStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
					response.AppendHeader("Content-Encoding", "gzip");
				} else {
					response.Filter = new System.IO.Compression.DeflateStream(response.Filter, System.IO.Compression.CompressionMode.Compress);
					response.AppendHeader("Content-Encoding", "deflate");
				}
			}
		}

		private void setHeadersForAll(HttpResponse response, string eTag) {
			response.Clear();
			response.Cache.SetCacheability(HttpCacheability.Public);
			response.Cache.SetExpires(DateTime.Now.AddDays(1));
			response.Cache.SetETag(eTag);
		}

		private void setHeadersForImage(HttpResponse response, string fileExtension) {
			response.ContentType = "image/" + fileExtension;
			response.Cache.SetAllowResponseInBrowserHistory(true);
		}

		private void setIsNotModified(HttpResponse response) {
			response.StatusCode = 304;
			response.StatusDescription = "Not Modified";
			response.Headers.Add("Content-Length", "0");
			//response.End();
		}

		private void setTags(string fileNameWithExtenstion, Size imageSize, HttpRequest request) {
			if(HttpContext.Current.Request["nocache"] == "1") return;
			string eTag = this.getGuid(fileNameWithExtenstion);
			if(imageSize != Size.Empty)
				eTag += imageSize.ToString();
			if(this.getWatermarks().Length > 0) {
				eTag += string.Join("-", this.getWatermarks().Select(w => w.ToString()).ToArray()).GetMd5();
			}

			string eTagRequest = request.Headers["If-None-Match"];

			this.eTag = eTag;
			this.eTagRequest = eTagRequest;

		}

		private ImageFormat getImageFormat(string fileExtension) {
			ImageFormat format = null;
			switch(fileExtension) {
				case "gif": format = ImageFormat.Gif; break;
				case "png": format = ImageFormat.Png; break;
				default: format = ImageFormat.Jpeg; break;
			}
			return format;
		}

		private void writeImageToResponse(Image image, ImageFormat imageFormat, HttpResponse response) {

			long imageSizeLimit = 102400; // w kb to jest 10MB
			if(!string.IsNullOrEmpty(HttpContext.Current.Request["size"])) {
				if(!Int64.TryParse(HttpContext.Current.Request["size"], out imageSizeLimit))
					imageSizeLimit = 102400;
			}
			imageSizeLimit = imageSizeLimit * 1024;// zamiana na bajty
			long quality = 97L;
			using(image) {
				while(true) {
					using(MemoryStream mst = new MemoryStream()) {
						if(image != null) {
							EncoderParameters parameters = new EncoderParameters(1);
							parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
							image.Save(mst, GetEncoder(imageFormat), parameters);
							if(mst.Length > imageSizeLimit) {
								mst.Dispose();
								quality--;
								continue;
							}
							mst.WriteTo(response.OutputStream);
							mst.Dispose();
							break;
						}
					}
				}
			}
			//response.End();
		}

		#endregion

	}
}